/*
 Copyright (c) [Year] [name of copyright holder]
 [Software Name] is licensed under Mulan PSL v2.
 You can use this software according to the terms and conditions of the Mulan
 PSL v2.
 You may obtain a copy of Mulan PSL v2 at:
          http://license.coscl.org.cn/MulanPSL2
 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
 KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 See the Mulan PSL v2 for more details.
*/

#include "PasteAdjustImg.hpp"

// ----------以下是构造函数和析构函数----------
/**
 * @brief 构造函数：初始化PasteAdjustImg对象
 *
 * 该构造函数用于通过指定的源图像、网格行数、网格列数、颜色、线条粗细、线条类型和窗口名称来初始化PasteAdjustImg对象。
 *
 * @param src 输入的源图像
 * @param rows 网格的行数
 * @param cols 网格的列数
 * @param color 绘制网格线的颜色
 * @param thickness 网格线的粗细
 * @param lineType 网格线的类型
 * @param windowName 显示图像的窗口名称，如果为空，则默认为"PasteAdjustImg"
 */
PasteAdjustImg::PasteAdjustImg(const cv::Mat &src, const int rows, const int cols, const cv::Scalar &color,
                               const int thickness, const int lineType, const std::string &windowName)
    : srcImg(src) {
    // 检查输入图像是否为空
    if (src.empty()) {
        throw std::invalid_argument("[PasteAdjustImg] 输入图像为空");
    }
    // 未经处理输出图像等于源图像
    dstImg = srcImg.clone();
    // 复制当前图像，以便在不修改原始图像的情况下进行操作
    tempImg = dstImg.clone();
    // 根据提供的参数初始化网格数据
    ChangeGrid(rows, cols, color, thickness, lineType);
    // 设置窗口名称，如果未提供或为空，则使用默认名称
    winName = (windowName.empty() ? "PasteAdjustImg" : windowName);
}

/**
 * @brief 构造函数：初始化PasteAdjustImg对象
 *
 * 该构造函数用于通过指定的图像文件路径、网格行数、网格列数、颜色、线条粗细、线条类型和窗口名称来初始化PasteAdjustImg对象。
 *
 * @param path 图像文件的路径，不能为空
 * @param rows 网格的行数
 * @param cols 网格的列数
 * @param color 绘制网格线使用的颜色
 * @param thickness 网格线的粗细
 * @param lineType 网格线的类型
 * @param windowName 显示图像的窗口名称，如果为空，则默认为"PasteAdjustImg"
 */
PasteAdjustImg::PasteAdjustImg(const std::string &path, const int rows, const int cols, const cv::Scalar &color,
                               const int thickness, const int lineType, const std::string &windowName)
    : srcImg(cv::imread(path)) {
    // 检查路径是否为空
    if (path.empty()) {
        throw std::invalid_argument("[PasteAdjustImg] 图像路径不能为空");
    }
    // 检查图像是否成功加载
    if (srcImg.empty()) {
        throw std::runtime_error("[PasteAdjustImg] 无法加载图像文件: " + path);
    }
    // 未经处理输出图像等于源图像
    dstImg = srcImg.clone();
    // 复制当前图像，以便在不修改原始图像的情况下进行操作
    tempImg = dstImg.clone();
    // 根据提供的参数初始化网格数据
    ChangeGrid(rows, cols, color, thickness, lineType);
    // 设置窗口名称，如果未提供或为空，则使用默认名称
    winName = (windowName.empty() ? "PasteAdjustImg" : windowName);
}

PasteAdjustImg::~PasteAdjustImg() {}
// ----------以上是构造函数和析构函数----------

// ----------以下是公有函数----------

// 获取调整完成的目标图像
cv::Mat PasteAdjustImg::GetDstImg() const { return dstImg; }

// 移除图像中的空白区域
int PasteAdjustImg::RemoveBlank() {
    // 定义移动像素量，用于调整文本区域
    int movePX = 5;
    // 定义标志位，用于控制循环退出
    bool flag = true;
    // 计算每个单元格的宽度和高度
    int cellWidth = dstImg.cols / gridCols;
    int cellHeight = dstImg.rows / gridRows;
    // 定义文本区域的宽度和高度
    int textWidth = cellWidth;
    int textHeight = cellWidth / 4;

    // 定义有效区域的尺寸和坐标，用于调整文本区域
    int usefulWidth = cellWidth / 5 * 5;
    int usefulHeight = cellHeight / 5 * 5;
    int usefulX = (cellWidth - usefulWidth) / 2;
    int usefulY = 0;

    // 刷新图像的lambda函数
    auto refreshImg = [&]() {
        // 复制当前图像，以便在不修改原始图像的情况下进行操作
        tempImg = dstImg.clone();

        // 白底空图贴文字的区域
        int whiteWidth = usefulWidth;
        int whiteHeight = usefulWidth / 4;

        // 遍历网格的每一行
        for (int i = 0; i < gridRows; ++i) {
            // 遍历网格的每一列
            for (int j = 0; j < gridCols; ++j) {
                // 定义当前单元格的区域
                cv::Rect cellRoi(j * cellWidth + usefulX, i * cellHeight + usefulY, usefulWidth, usefulHeight);
                // 定义当前单元格的绘制文本区域
                cv::Rect textRoi(j * cellWidth + usefulX, i * cellHeight + usefulY, whiteWidth, whiteHeight);
                // 将当前索引对应的文本图像区域绘制白色
                cv::rectangle(tempImg, textRoi, cv::Scalar(255, 255, 255), -1);
                //  在单元格区域周围绘制矩形框，以突出显示文本区域
                cv::rectangle(tempImg, cellRoi, gridColor, gridThickness);
            }
        }
        // 更新窗口中显示的图像
        cv::imshow(winName, tempImg);
        std::cout << "\r cell W x H :" << usefulWidth << " x " << usefulHeight << "     ";
    };

    // 显示效果图像的lambda函数，用户可以决定是否应用该特效
    auto showEffect = [&]() -> bool {
        // 创建一个新的白色临时图像
        cv::Mat tempImg2 = cv::Mat(gridRows * usefulHeight, gridCols * usefulWidth, CV_8UC3, cv::Scalar(255, 255, 255));

        // 循环复制目标图像的指定区域到临时图像的指定区域
        for (int i = 0; i < gridRows; ++i) {
            for (int j = 0; j < gridCols; ++j) {
                cv::Rect oldRoi(j * cellWidth + usefulX, i * cellHeight + usefulY, usefulWidth, usefulHeight);
                cv::Rect newRoi(j * usefulWidth, i * usefulHeight, usefulWidth, usefulHeight);

                cv::Mat oldImgRoi = dstImg(oldRoi);
                cv::Mat newImgRoi = tempImg2(newRoi);

                oldImgRoi.copyTo(newImgRoi);
            }
        }

        // tempImg2复制到tempImg中供展示效果，tempImg2用于保存可能的应用效果
        tempImg = tempImg2.clone();

        // 绘制网格，以便更清晰地观察变换效果
        DrawGrid(cv::Point(0, 0), cv::Point(tempImg.cols, tempImg.rows));

        // 显示变换后的图像
        cv::imshow(winName, tempImg);
        // 提示用户如何操作
        std::cout << "[esc]还原 [enter]确认" << std::endl;

        while (true) {
            // 获取用户输入的按键
            char c = cv::waitKey(200);

            // 根据用户输入的按键，执行相应的操作
            switch (c) {
            case -1: // 无操作
                continue;
            case 27: // [esc] 还原
                std::cout << "已还原" << std::endl;
                return false;
            case 13: // [enter] 确认
                // 应用变换效果
                dstImg = tempImg2.clone();
                std::cout << "已应用变换效果" << std::endl;
                return true;
            default: // 默认提示信息
                std::cout << "[esc]还原 [enter]确认" << std::endl;
                break;
            }
        }
    };

    // 刷新图像
    refreshImg();

    while (flag) {
        // 获取用户输入的按键
        char c = cv::waitKey(200);

        // 根据用户输入的命令字符，执行相应的操作
        switch (c) {
        case -1: // 无操作
            continue;
        case 27: // [esc] 退出
            std::cout << "[esc] 退出" << std::endl;
            return -1;
        case 13: // [enter] 预览
            if (showEffect()) {
                flag = false;
                continue;
            }
            break;
        case '-': // 减少移动像素
            movePX = std::max(1, movePX - 1);
            std::cout << "移动像素减至:" << movePX << "px" << std::endl;
            continue;
        case '+': // 增加移动像素
            movePX = std::min(20, movePX + 1);
            std::cout << "移动像素增至:" << movePX << "px" << std::endl;
            continue;
        case '0': // 还原
            usefulWidth = cellWidth / 5 * 5;
            usefulHeight = cellHeight / 5 * 5;
            usefulX = (cellWidth - usefulWidth) / 2;
            usefulY = 0;
            break;
        case 'w': // 垂直放大
            if (usefulHeight < cellHeight) {
                if (usefulY > movePX) {
                    usefulY -= movePX;
                    usefulHeight += movePX;
                } else if (usefulY > 0) {
                    --usefulY;
                    ++usefulHeight;
                }
            }
            break;
        case 's': // 垂直缩小
            if (usefulHeight > movePX) {
                usefulY += movePX;
                usefulHeight -= movePX;
            } else if (usefulHeight > 2) {
                ++usefulY;
                --usefulHeight;
            }
            break;
        case 'a': // 水平放大
            if (usefulX > 0) {
                usefulWidth += movePX;
                usefulX = (cellWidth - usefulWidth) / 2;
            }
            break;
        case 'd': // 水平缩小
            if (usefulWidth > 2) {
                usefulWidth -= movePX;
                usefulX = (cellWidth - usefulWidth) / 2;
            }
            break;
        case '8': // 上移
            if (usefulY > movePX) {
                usefulY -= movePX;
            } else if (usefulY > 0) {
                --usefulY;
            }
            break;
        case '2': // 下移
            if (usefulY + usefulHeight + movePX < cellHeight) {
                usefulY += movePX;
            } else if (usefulY + usefulHeight < cellHeight) {
                ++usefulY;
            }
            break;
        default:
            std::cout << "[ a d ] 水平放大缩小" << std::endl;
            std::cout << "[ w s ] 垂直放大缩小 " << std::endl;
            std::cout << "[ 8 2 ] 上下移动 " << std::endl;
            std::cout << "[ + - ] 增减移动间距" << std::endl;
            std::cout << "[esc]退出 [enter]预览 [0]恢复原样" << std::endl;
            continue;
        }
        // 刷新图像
        refreshImg();
    }
    return 0;
}

// 修整图像边缘
int PasteAdjustImg::TrimEdge() {
    bool flag = true;            // 退出循环标志
    bool isOK = false;           // 移动成功标记
    int movePX = 1;              // 移动像素
    int pointIndex = 0;          // 当前操作的顶点索引 [0] 左上 [1] 右下 [2] 两点
    int imgWidth = dstImg.cols;  // 图像的宽度
    int imgHeight = dstImg.rows; // 图像的高度

    // 初始化网格的两个顶点
    cv::Point gridPoint1 = cv::Point(0, 0);
    cv::Point gridPoint2 = cv::Point((imgWidth / gridCols) * gridCols, (imgHeight / gridRows) * gridRows);

    // 用于根据条件移动坐标轴的lambda函数
    auto moveAxis = [&](int &axis1, int &axis2, const int &base, bool isAdd) {
        // 根据isAdd的值决定tempPX的值，用于后续的坐标轴移动
        int tempPX = (isAdd ? movePX : -movePX);

        // 根据pointIndex的值选择不同的坐标轴进行移动
        if (pointIndex == 0) {
            // 如果pointIndex为0，根据tempPX的值和base参数移动axis1坐标轴
            axis1 += tempPX * base;
        } else if (pointIndex == 1) {
            // 如果pointIndex为1，根据tempPX的值和base参数移动axis2坐标轴
            axis2 += tempPX * base;
        } else {
            // 如果pointIndex为其他值，直接根据tempPX的值移动axis1坐标轴两次
            axis1 += tempPX;
            axis2 += tempPX;
        }
    };

    // 刷新图像的lambda函数
    auto refreshImg = [&]() {
        try {
            // 复制目标图像到临时变量，用于后续操作
            tempImg = dstImg.clone();
            // 调用绘制网格函数，对临时图像进行处理
            DrawGrid(gridPoint1, gridPoint2);
            // 可移动的两边重新画条反色线,更容易分辨
            if (pointIndex == 0) {
                cv::line(tempImg, gridPoint1, cv::Point(gridPoint1.x, gridPoint2.y), cv::Scalar(255, 255, 255) - gridColor,
                         gridThickness, gridLineType);
                cv::line(tempImg, gridPoint1, cv::Point(gridPoint2.x, gridPoint1.y), cv::Scalar(255, 255, 255) - gridColor,
                         gridThickness, gridLineType);
            } else if (pointIndex == 1) {
                cv::line(tempImg, cv::Point(gridPoint1.x, gridPoint2.y), gridPoint2, cv::Scalar(255, 255, 255) - gridColor,
                         gridThickness, gridLineType);
                cv::line(tempImg, cv::Point(gridPoint2.x, gridPoint1.y), gridPoint2, cv::Scalar(255, 255, 255) - gridColor,
                         gridThickness, gridLineType);
            }
            // 在窗口中显示处理后的临时图像
            cv::imshow(winName, tempImg);
        } catch (const cv::Exception &e) {
            // 捕获并处理OpenCV异常
            std::cerr << "[PasteAdjustImg::Adjust] OpenCV 错误: " << e.what() << std::endl;
        }
    };

    // 显示效果图像的lambda函数，用户可以决定是否应用该特效
    auto showEffect = [&]() -> bool {
        // 计算两个网格点之间的最小和最大坐标
        int minX = std::min(gridPoint1.x, gridPoint2.x);
        int minY = std::min(gridPoint1.y, gridPoint2.y);
        int maxX = std::max(gridPoint1.x, gridPoint2.x);
        int maxY = std::max(gridPoint1.y, gridPoint2.y);

        // 计算特效图像的宽度和高度
        int imgWidth = maxX - minX;
        int imgHeight = maxY - minY;

        // 特效图像和目标图像的矩形区域
        cv::Rect tempImgRect(0, 0, imgWidth, imgHeight);
        cv::Rect dstImgRect(minX, minY, imgWidth, imgHeight);

        // 调整矩形区域以确保它们在目标图像的范围内
        if (minX < 0) // 左越界
        {
            tempImgRect.x -= minX;
            tempImgRect.width += minX;
            dstImgRect.x = 0;
            dstImgRect.width += minX;
        }
        if (minY < 0) //  上越界
        {
            tempImgRect.y -= minY;
            tempImgRect.height += minY;
            dstImgRect.y = 0;
            dstImgRect.height += minY;
        }
        int tempPX = dstImg.cols - maxX;
        if (tempPX < 0) // 右越界
        {
            tempImgRect.width += tempPX;
            dstImgRect.width += tempPX;
        }
        tempPX = dstImg.rows - maxY;
        if (tempPX < 0) // 下越界
        {
            tempImgRect.height += tempPX;
            dstImgRect.height += tempPX;
        }

        // 创建一个白色临时图像
        cv::Mat tempImg2 = cv::Mat(imgHeight, imgWidth, CV_8UC3, cv::Scalar(255, 255, 255));

        // 复制目标图像的指定区域到临时图像
        dstImg(dstImgRect).copyTo(tempImg2(tempImgRect));

        // tempImg2复制到tempImg中供展示效果，tempImg2用于保存可能的应用效果
        tempImg = tempImg2.clone();

        // 旧的点保存一下还原时还用
        cv::Point p1(gridPoint1);
        cv::Point p2(gridPoint2);
        // 更新网格点以匹配特效图像的尺寸
        gridPoint1 = cv::Point(0, 0);
        gridPoint2 = cv::Point(imgWidth, imgHeight);
        // 绘制网格，以便更清晰地观察变换效果
        DrawGrid(gridPoint1, gridPoint2);

        // 显示变换后的图像
        cv::imshow(winName, tempImg);
        // 提示用户如何操作
        std::cout << "[esc]还原 [enter]确认 [0]确认并梯形校正" << std::endl;

        while (true) {
            // 获取用户输入的按键
            char c = cv::waitKey(200);

            // 根据用户输入的按键，执行相应的操作
            switch (c) {
            case -1: // 无操作
                continue;
            case 27: // [esc] 还原
                // 还原网格点位置和临时图像
                gridPoint1 = p1;
                gridPoint2 = p2;
                // tempImg = dstImg.clone();
                std::cout << "已还原" << std::endl;
                return false;
            case 13: // [enter] 确认
                // 应用变换效果
                dstImg = tempImg2.clone();
                std::cout << "已应用变换效果" << std::endl;
                return true;
            case '0': // [0] 确认
                // 应用变换效果

                dstImg = tempImg2.clone();
                std::cout << "已应用变换效果" << std::endl;
                // 梯形校正
                CorrectImg();
                return true;
            default: // 默认提示信息
                std::cout << "[esc]还原 [enter]确认 [0]确认并梯形校正" << std::endl;
                break;
            }
        }
    };

    // 初始刷新图像
    refreshImg();

    // 主循环，等待用户输入并进行相应操作
    while (flag) {
        // 获取用户输入的按键
        char c = cv::waitKey(200);

        // 根据用户输入的命令字符，执行相应的操作
        switch (c) {
        case -1: // 无操作
            continue;
        case 27: // [esc] 退出
            std::cout << "[esc] 退出" << std::endl;
            return -1;
        case 13: // [enter] 预览
            if (showEffect()) {
                flag = false;
                continue;
            }
            break;
        case '-': // 减少移动像素
            movePX = std::max(1, movePX - 1);
            std::cout << "移动像素减至:" << movePX << "px" << std::endl;
            continue;
        case '+': // 增加移动像素
            movePX = std::min(20, movePX + 1);
            std::cout << "移动像素增至:" << movePX << "px" << std::endl;
            continue;
        case '0': // 还原
            gridPoint1 = cv::Point(0, 0);
            gridPoint2 = cv::Point((imgWidth / gridCols) * gridCols, (imgHeight / gridRows) * gridRows);
            std::cout << "已恢复原始大小" << std::endl;
            break;
        case 'w': // 上移
            moveAxis(gridPoint1.y, gridPoint2.y, gridRows, false);
            break;
        case 's': // 下移
            moveAxis(gridPoint1.y, gridPoint2.y, gridRows, true);
            break;
        case 'a': // 左移
            moveAxis(gridPoint1.x, gridPoint2.x, gridCols, false);
            break;
        case 'd': // 右移
            moveAxis(gridPoint1.x, gridPoint2.x, gridCols, true);
            break;
        case ' ': // 换边
            pointIndex = ((pointIndex == 2) ? 0 : pointIndex + 1);
            if (pointIndex == 2)
                movePX = 5;
            else if (pointIndex == 0)
                movePX = 1;
            break;
        default: // 默认提示信息
            std::cout << "[ w s a d ] 上下左右移动" << std::endl;
            std::cout << "[ 空格 ] 换调整点 " << std::endl;
            std::cout << "[ + - ] 增减移动间距" << std::endl;
            std::cout << "[esc]退出 [enter]预览 [0]恢复原样" << std::endl;
            continue;
        }
        refreshImg();
    }
    return 0;
}

/**
 * @brief 更改或设置网格参数,此时并未在图像上实际绘制网格
 *
 * @param rows 网格的行数，必须大于等于 1 且小于原图像的行数
 * @param cols 网格的列数，必须大于等于 1 且小于原图像的列数
 * @param color 网格线的颜色，使用cv::Scalar结构体表示
 * @param thickness 网格线的宽度，非负整数
 * @param lineType 线型参数，线型必须是4, 8或cv::LINE_AA
 */
void PasteAdjustImg::ChangeGrid(const int rows, const int cols, const cv::Scalar &color, const int thickness,
                                const int lineType) {
    // 检查行数和列数是否有效
    if (rows < 1 || cols < 1 || rows >= dstImg.rows || cols >= dstImg.cols) {
        std::cout << "rows" << rows << "cols" << cols << std::endl;
        std::cout << "dstImg.rows" << dstImg.rows << "dstImg.cols" << dstImg.cols << std::endl;
        throw std::invalid_argument("[PasteAdjustImg::ChangeGrid] 行数和列数必须大于等于 1 且 小于原图像行数和列数");
    }
    // 检查网格线宽是否有效
    if (thickness < 0) {
        throw std::invalid_argument("[PasteAdjustImg::ChangeGrid] 网格线宽必须非负");
    }
    // 检查线型参数是否有效
    if (lineType != 4 && lineType != 8 && lineType != cv::LINE_AA) {
        throw std::invalid_argument("[PasteAdjustImg::ChangeGrid] 线型必须是4, 8或cv::LINE_AA");
    }
    // 检查颜色值是否有效
    for (int i = 0; i < 4; ++i) {
        if (!std::isfinite(color[i])) {
            throw std::invalid_argument("[PasteAdjustImg::ChangeGrid] 颜色值包含无效数值");
        }
    }
    // 设置网格属性
    gridRows = rows;
    gridCols = cols;
    gridThickness = thickness;
    gridLineType = lineType;
    gridColor = color;
}

/**
 * @brief 在图像网格上绘制文本标签
 *
 * @param textList 包含要绘制的文本的字符串向量。每个字符串代表一行文本。
 * @param isTwoRows 指示文本是否应分布在两行。如果是true，则文本将被分成两行显示。
 * @param textColor 指定文本的颜色。使用cv::Scalar结构体，通常包含BGR值。
 * @param bgColor 指定文本背景区域的颜色。使用cv::Scalar结构体，通常包含BGR值。
 * @param fontFace 指定文本的字体。应使用OpenCV定义的字体常量。
 * @param thickness 指定文本线条的厚度。以像素为单位。
 * @param fontScale 指定文本的缩放比例。这是一个浮点数，表示文本的大小。
 */
int PasteAdjustImg::DrawASKII(const std::vector<std::string> &textList, const bool isTwoRows, const cv::Scalar &textColor,
                              const cv::Scalar &bgColor, const int fontFace, const int thickness, const double fontScale) {
    // 检查文本列表是否为空，如果为空则抛出异常
    if (textList.empty()) {
        throw std::invalid_argument("[PasteAdjustImg::DrawText] 文本列表为空");
    }
    // 根据是否为两行文本，计算需要绘制的文本数量
    int count;
    if (isTwoRows) {
        count = textList.size() / 2;
    } else {
        count = textList.size();
    }
    // 如果计算后的文本数量小于1，则抛出异常
    if (count < 1) {
        throw std::invalid_argument("[PasteAdjustImg::DrawText] 文本列表为空");
    }

    // 定义生成文本标签图像的宽度和高度
    int tagWidth = 1200;
    int tagHeight = 300;

    // 创建一个存储文本图像的向量，并预先分配空间以提高性能
    std::vector<cv::Mat> textImgList;
    textImgList.reserve(count);

    // 根据是否为两行文本，执行不同的绘制逻辑
    if (isTwoRows) {
        // 循环生成每一对图像
        for (int i = 0; i < count; ++i) {
            // 获取当前图像要绘制的两个文本字符串
            std::string text1 = textList[i * 2];
            std::string text2 = textList[i * 2 + 1];

            // 如果文本字符串为空或"0"，则跳过该对图像的生成
            if (text1.empty() || text1 == "0") {
                textImgList.push_back(cv::Mat());
                continue;
            }

            // 获取文本字符串的尺寸信息
            cv::Size textSize1 = cv::getTextSize(text1, fontFace, fontScale, thickness, nullptr);
            cv::Size textSize2 = cv::getTextSize(text2, fontFace, fontScale, thickness, nullptr);

            // 计算文本在图像上的绘制位置
            cv::Point point1((tagWidth - textSize1.width) / 2, (tagHeight + textSize1.height) / 3);
            cv::Point point2((tagWidth - textSize2.width) / 2, (tagHeight + textSize2.height) * 2 / 3);

            // 创建一个空白图像并填充背景颜色
            cv::Mat textImg(tagHeight, tagWidth, CV_8UC3, bgColor);

            // 在图像上绘制文本
            cv::putText(textImg, text1, point1, fontFace, fontScale, textColor, thickness, cv::LINE_AA);
            cv::putText(textImg, text2, point2, fontFace, fontScale, textColor, thickness, cv::LINE_AA);

            // 将生成的图像添加到输出列表中
            textImgList.push_back(textImg);
        }
    } else {
        // 循环生成每一对图像
        for (int i = 0; i < count; ++i) {
            // 获取当前图像要绘制的文本字符串
            std::string text1 = textList[i];

            // 如果文本字符串为空或"0"，则跳过该对图像的生成
            if (text1.empty() || text1 == "0") {
                textImgList.push_back(cv::Mat());
                continue;
            }

            // 获取文本字符串的尺寸信息
            cv::Size textSize1 = cv::getTextSize(text1, fontFace, fontScale, thickness, nullptr);

            // 计算文本在图像上的绘制位置
            cv::Point point1((tagWidth - textSize1.width) / 2, (tagHeight + textSize1.height) / 2);

            // 创建一个空白图像并填充背景颜色
            cv::Mat textImg(tagHeight, tagWidth, CV_8UC3, bgColor);

            // 在图像上绘制文本
            cv::putText(textImg, text1, point1, fontFace, fontScale, textColor, thickness, cv::LINE_AA);

            // 将生成的图像添加到输出列表中
            textImgList.push_back(textImg);
        }
    }

    // 计算每个单元格的宽度和高度
    int cellWidth = tempImg.cols / gridCols;
    int cellHeight = tempImg.rows / gridRows;
    // 定义实际文本图像的宽度和高度
    int textWidth = cellWidth;
    int textHeight = cellWidth / 4;

    // 调整文本图像列表中的图像大小
    try {
        for (auto &item : textImgList) {
            if (!item.empty()) {
                cv::resize(item, item, cv::Size(textWidth, textHeight), 0, 0, cv::INTER_LINEAR);
            }
        }
    } catch (const cv::Exception &e) {
        throw std::runtime_error("[PasteAdjustImg::DrawText] 调整图像大小时出错");
    }

    // 将文本图像复制到目标图像的指定位置
    int index = 0;
    for (int i = 0; i < gridRows; ++i) {
        for (int j = 0; j < gridCols; ++j) {
            if (index < count && !textImgList[index].empty()) {
                try {
                    // 定义当前单元格的区域
                    cv::Mat imgRoi = tempImg(cv::Rect(j * cellWidth, i * cellHeight, textWidth, textHeight));
                    // 将当前索引对应的文本图像复制到单元格区域中
                    textImgList[index].copyTo(imgRoi);
                } catch (const cv::Exception &e) {
                    throw std::runtime_error("[PasteAdjustImg::DrawText] 将图像复制到ROI时出错" + std::string(e.what()));
                }
            }
            ++index;
        }
    }
    DrawGrid(cv::Point(0, 0), cv::Point(tempImg.cols, tempImg.rows));
    // 显示变换后的图像
    cv::imshow(winName, tempImg);
    // 提示用户如何操作
    std::cout << "[esc]还原 [enter]确认" << std::endl;

    while (true) {
        // 获取用户输入的按键
        char c = cv::waitKey(200);

        // 根据用户输入的命令字符，执行相应的操作
        switch (c) {
        case -1: // 无操作
            continue;
        case 27: // [esc] 退出
            std::cout << "[esc] 退出" << std::endl;
            return -1;
        case 13: // [enter] 确认
            // 应用变换效果
            dstImg = tempImg.clone();
            std::cout << "已应用变换效果" << std::endl;
            return 0;
        default: // 默认提示信息
            std::cout << "[esc]退出 [enter]确认" << std::endl;
            break;
        }
    }
}

/**
 * @brief 保存当前处理的图像到指定路径。
 *
 * 该函数负责将当前处理后的图像（dstImg）保存到指定的文件路径。如果路径无效或保存失败，会抛出异常。
 *
 * @param path 图像保存的目标路径，不能为空。路径应为有效的文件路径，包括目录和文件名扩展名。
 */
void PasteAdjustImg::SaveImg(const std::string &path) {
    // 验证文件路径是否为空
    if (path.empty()) {
        // 如果路径为空，抛出无效参数异常
        throw std::invalid_argument("[PasteAdjustImg::SaveImg] 文件路径无效");
    }

    // 尝试保存图像并检查返回值
    if (cv::imwrite(path, dstImg)) {
        // 如果图像保存成功，输出成功信息到标准输出
        std::cout << "图像已成功保存到:" << path << std::endl;
    } else { // 如果图像保存失败，抛出运行时错误异常
        throw std::runtime_error("[PasteAdjustImg::SaveImage] 图像保存失败");
    }
}

/**
 * @brief 将图像分割并保存到指定目录
 *
 * 本函数根据提供的文件名列表、目录和扩展名，将图像分割成多个小块，并将每个小块保存为单独的文件。
 * 分割是基于gridRows和gridCols进行的，这两个变量定义了图像将被分割成多少行和多少列。
 *
 * @param fileNameList 包含分割后图像文件名的字符串向量
 * @param dir 保存分割后图像的目录路径
 * @param extension 分割后图像的文件扩展名
 *
 * @throws std::invalid_argument 如果目录路径无效或目录不存在，抛出此异常
 * @throws std::invalid_argument 如果文件扩展名无效，抛出此异常
 * @throws std::runtime_error 如果保存图像文件失败，抛出此异常
 */
void PasteAdjustImg::SplitSaveImg(const std::vector<std::string> &fileNameList, const std::string &dir,
                                  const std::string &extension) {
    // 检查目录路径是否为空
    if (dir.empty()) {
        throw std::invalid_argument("[PasteAdjustImg::SplitSaveImg] 文件路径无效");
    }
    // 确保目录路径以路径分隔符结尾
    std::string dirStr = dir;
    if (dirStr.back() != '\\' && dirStr.back() != '/') {
        dirStr += '\\';
    }

    // 验证文件扩展名是否有效，并转换为小写
    std::string exten = extension;
    std::transform(exten.begin(), exten.end(), exten.begin(), ::tolower);
    if (exten != ".bmp" && exten != ".jpg" && exten != ".png" && exten != ".jpeg") {
        throw std::invalid_argument("[PasteAdjustImg::SplitSaveImg] 扩展名无效" + extension);
    }

    // 初始化索引和计数变量
    int index = 0;
    int count = fileNameList.size();
    // 计算每个分割块的宽度和高度
    int cellWidth = dstImg.cols / gridCols;
    int cellHeight = dstImg.rows / gridRows;

    // 遍历每一行和每一列，分割并保存图像
    for (int i = 0; i < gridRows; ++i) {
        for (int j = 0; j < gridCols; ++j) {
            // 检查索引是否超出范围或当前文件名无效
            if (index >= count || fileNameList[index].empty() || fileNameList[index] == "0") {
                ++index;
                continue;
            }
            // 定义当前分割块的区域
            cv::Rect roi(j * cellWidth, i * cellHeight, cellWidth, cellHeight);
            cv::Mat roiImg = dstImg(roi);

            // 构造初始文件路径
            std::string path = dirStr + fileNameList[index] + extension;

            // 如果文件已存在，构造新的文件路径，直到找到一个不存在的文件名
            int fileIndex = 0;
            while (std::ifstream(path)) {
                path = dirStr + fileNameList[index] + "-" + std::to_string(++fileIndex) + extension;
            }

            // 尝试保存图像并检查返回值
            if (cv::imwrite(path, roiImg)) {
                // 如果图像保存成功，输出成功信息到标准输出
                std::cout << "图像已成功保存到:" << path << std::endl;
            } else { // 如果图像保存失败，抛出运行时错误异常
                throw std::runtime_error("[PasteAdjustImg::SplitSaveImg] 图像保存失败");
            }
            // 移动到下一个文件名
            ++index;
        }
    }
}

/**
 * @brief 该函数用于调整图像大小
 * 将源图像宽和高缩小到原来的一半(整体图像缩小到1/4)，然后保存到指定路径
 *
 * @param srcPath 图像源文件路径
 * @param destPath 调整大小后的图像保存路径
 */
void PasteAdjustImg::ImgResize(const std::string &srcPath, const std::string &destPath) {
    // 读取源图像
    cv::Mat srcImg = cv::imread(srcPath);
    // 检查图像是否成功加载
    if (srcImg.empty()) {
        std::cerr << "无法从路径加载图像: " << srcPath << std::endl;
        return;
    }

    // 准备用于存储调整大小后图像的矩阵
    cv::Mat destImg;
    // 缩小用INTER_AREA插值算法更好
    cv::resize(srcImg, destImg, cv::Size(srcImg.cols / 2, srcImg.rows / 2), 0, 0, cv::INTER_AREA);
    // 检查图像是否成功保存
    if (!cv::imwrite(destPath, destImg)) {
        std::cerr << "未能将图像保存到路径: " << destPath << std::endl;
    }
    // 显式释放资源
    srcImg.release();
    destImg.release();
}

// ----------以上是公有函数----------

// ----------以下是私有函数----------
// 校正图像的函数，用于调整图像的透视角度
void PasteAdjustImg::CorrectImg() {
    bool flag = true;            // 退出循环标志
    int movePX = 5;              // 移动像素距离
    int pointIndex = 0;          // 当前操作的顶点索引
    int imgWidth = dstImg.cols;  // 图像的宽度
    int imgHeight = dstImg.rows; // 图像的高度

    // 检查图像尺寸是否有效
    if (imgWidth <= 0 || imgHeight <= 0) {
        throw std::invalid_argument("[PasteAdjustImg::CorrectImg] 图像高度和宽度必须大于 0");
    }

    // 定义图像的四个顶点
    cv::Point aPoint(0, 0);                // 左上角点
    cv::Point bPoint(imgWidth, 0);         // 右上角点
    cv::Point cPoint(0, imgHeight);        // 左下角点
    cv::Point dPoint(imgWidth, imgHeight); // 右下角点

    // 获取顶点的lambda函数
    auto getPoint = [&]() -> cv::Point & { // 根据pointIndex的值选择不同的点
        switch (pointIndex) {
        case 0: // 当pointIndex为0时，返回aPoint的引用
            return aPoint;
        case 1: // 当pointIndex为1时，返回bPoint的引用
            return bPoint;
        case 2: // 当pointIndex为2时，返回cPoint的引用
            return cPoint;
        case 3: // 当pointIndex为3时，返回dPoint的引用
            return dPoint;
        default: // 当pointIndex为其他值时，默认返回aPoint的引用
            return aPoint;
        }
    };

    // 移动顶点的lambda函数
    auto movePoint = [&](char direction) {
        // 获取并引用当前点，以便直接修改其坐标
        cv::Point &p = getPoint();

        // 根据给定的方向字符，决定点的移动方向
        switch (direction) {
        case 'u': // 向上移动，确保y坐标不小于0
            p.y = std::max(0, p.y - movePX);
            break;
        case 'd': // 向下移动，确保y坐标不超过图像高度
            p.y = std::min(imgHeight, p.y + movePX);
            break;
        case 'l': // 向左移动，确保x坐标不小于0
            p.x = std::max(0, p.x - movePX);
            break;
        case 'r': // 向右移动，确保x坐标不超过图像宽度
            p.x = std::min(imgWidth, p.x + movePX);
            break;
        default: // 如果方向字符不符合预期，不进行任何操作
            break;
        }
    };

    // 刷新图像的lambda函数
    auto refreshImg = [&]() {
        // 克隆当前图像以避免修改原始图像
        tempImg = dstImg.clone();

        // 绘制连接aPoint和bPoint的线条
        cv::line(tempImg, aPoint, bPoint, gridColor, gridThickness, gridLineType);
        // 绘制连接cPoint和dPoint的线条
        cv::line(tempImg, cPoint, dPoint, gridColor, gridThickness, gridLineType);
        // 绘制连接aPoint和cPoint的线条
        cv::line(tempImg, aPoint, cPoint, gridColor, gridThickness, gridLineType);
        // 绘制连接bPoint和dPoint的线条
        cv::line(tempImg, bPoint, dPoint, gridColor, gridThickness, gridLineType);
        // 绘制一个填充的圆形，位置由getPoint()确定，半径为gridThickness的三倍
        cv::circle(tempImg, getPoint(), gridThickness * 3, gridColor, -1, gridLineType);

        // 显示绘制后的图像
        cv::imshow(winName, tempImg);
    };

    // 显示效果图像的lambda函数，用户可以决定是否应用该特效
    auto showEffect = [&]() -> bool {
        // 定义源图像的四个顶点
        cv::Point2f srcPoints[4] = {aPoint, bPoint, cPoint, dPoint};
        // 定义目标图像的四个顶点，形成一个矩形
        cv::Point2f dstPoints[4] = {cv::Point(0, 0), cv::Point(imgWidth, 0), cv::Point(0, imgHeight),
                                    cv::Point(imgWidth, imgHeight)};
        // 计算透视变换矩阵
        cv::Mat pt = cv::getPerspectiveTransform(srcPoints, dstPoints);

        // 将当前图像复制到临时图像，用于显示变换效果
        tempImg = dstImg.clone();

        // 应用透视变换
        cv::warpPerspective(tempImg, tempImg, pt, tempImg.size());

        // 再次复制临时图像，用于保存可能的应用效果
        cv::Mat tempImg2 = tempImg.clone();

        // 绘制网格，以便更清晰地观察变换效果
        DrawGrid(cv::Point(0, 0), cv::Point(tempImg.cols, tempImg.rows));

        // 显示变换后的图像
        cv::imshow(winName, tempImg);
        // 提示用户如何操作
        std::cout << "[esc]还原 [enter]确认" << std::endl;

        while (true) {
            // 获取用户输入的按键
            char c = cv::waitKey(200);

            // 根据用户输入的按键，执行相应的操作
            switch (c) {
            case -1: // 无操作
                continue;
            case 27: // [esc] 还原
                // 还原临时图像
                // tempImg = dstImg.clone();
                std::cout << "已还原" << std::endl;
                return false;
            case 13: // [enter] 确认
                // 应用变换效果
                dstImg = tempImg2.clone();
                std::cout << "已应用变换效果" << std::endl;
                return true;
            default: // 默认提示信息
                std::cout << "[esc]还原 [enter]确认" << std::endl;
                break;
            }
        }
    };

    // 初始绘制线条和顶点
    refreshImg();

    // 主循环，等待用户输入并进行相应操作
    while (flag) { // 获取用户输入的按键
        char c = cv::waitKey(200);

        // 根据用户输入的命令字符，执行相应的操作
        switch (c) {
        case -1: // 无操作
            continue;
        case 27: // [esc] 退出
            std::cout << "[esc] 退出" << std::endl;
            return;
        case 13: // [enter] 预览
            if (showEffect()) {
                flag = false;
                continue;
            }
            break;
        case '-': // 减少移动像素
            movePX = std::max(1, movePX - 1);
            std::cout << "移动像素减至:" << movePX << "px" << std::endl;
            continue;
        case '+': // 增加移动像素
            movePX = std::min(20, movePX + 1);
            std::cout << "移动像素增至:" << movePX << "px" << std::endl;
            continue;
        case '0': // 还原
            aPoint = cv::Point(0, 0);
            bPoint = cv::Point(imgWidth, 0);
            cPoint = cv::Point(0, imgHeight);
            dPoint = cv::Point(imgWidth, imgHeight);
            std::cout << "已恢复原始大小" << std::endl;
            break;
        case 'w': // 上移
            movePoint('u');
            break;
        case 's': // 下移
            movePoint('d');
            break;
        case 'a': // 左移
            movePoint('l');
            break;
        case 'd': // 右移
            movePoint('r');
            break;
        case ' ': // 换点
            pointIndex = (pointIndex == 3 ? 0 : pointIndex + 1);
            break;
        default: // 默认提示信息
            std::cout << "[ w s a d ] 上下左右移动点" << std::endl;
            std::cout << "[ 空格 ] 换调整点 " << std::endl;
            std::cout << "[ + - ] 增减移动间距" << std::endl;
            std::cout << "[esc]退出 [enter]显示效果 [0]恢复原样" << (int)c << std::endl;
            continue;
        }
        refreshImg();
    }
}

// 绘制网格
void PasteAdjustImg::DrawGrid(cv::Point point1, cv::Point point2) {
    // 计算每个网格单元的宽度
    int cellWidth = (point2.x - point1.x) / gridCols;
    // 计算每个网格单元的高度
    int cellHeight = (point2.y - point1.y) / gridRows;

    // 绘制水平网格线
    for (int i = 0; i <= gridRows; ++i) {
        // 计算当前水平线的y坐标
        int y = point1.y + i * cellHeight;
        // 绘制从左到右的水平线
        cv::line(tempImg, cv::Point(point1.x, y), cv::Point(point2.x, y), gridColor, gridThickness, gridLineType);
    }

    // 绘制垂直网格线
    for (int i = 0; i <= gridCols; ++i) {
        // 计算当前垂直线的x坐标
        int x = point1.x + i * cellWidth;
        // 绘制从上到下的垂直线
        cv::line(tempImg, cv::Point(x, point1.y), cv::Point(x, point2.y), gridColor, gridThickness, gridLineType);
    }
}
// ----------以上是私有函数----------
